// Filename: geoMipTerrain.I
// Created by:  rdb (29Jun07)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "config_grutil.h"

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::Constructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
INLINE GeoMipTerrain::
GeoMipTerrain(const string &name) {
  _root = NodePath(name);
  _root_flattened = false;
  _xsize = 0;
  _ysize = 0;
  _block_size = 16;
  _max_level = 4; // Always log(_block_size) / log(2.0)
  _min_level = 0;
  _factor = 100.0;
  _near = 16.0;
  _far = 128.0;
  _use_near_far = false;
  _has_color_map = false;
  PT(PandaNode) tmpnode = new PandaNode("tmp_focal");
  _auto_flatten = AFM_off;
  _focal_point = NodePath(tmpnode);
  _focal_is_temporary = true;
  _is_dirty = true;
  _bruteforce = false;
  _stitching = false;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::Destructor
//       Access: Published
//  Description: This will not remove the terrain node itself.
//               To have the terrain itself also deleted, please
//               call remove_node() prior to destruction.
////////////////////////////////////////////////////////////////////
INLINE GeoMipTerrain::
~GeoMipTerrain() {
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::heightfield
//       Access: Published
//  Description: Returns a reference to the heightfield (a PNMImage)
//               contained inside GeoMipTerrain.  You can use
//               the reference to alter the heightfield.
////////////////////////////////////////////////////////////////////
INLINE PNMImage &GeoMipTerrain::
heightfield() {
  return _heightfield;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::color_map
//       Access: Published
//  Description: Returns a reference to the color map (a PNMImage)
//               contained inside GeoMipTerrain.  You can use
//               the reference to alter the color map.
////////////////////////////////////////////////////////////////////
INLINE PNMImage &GeoMipTerrain::
color_map() {
  return _color_map;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::set_bruteforce
//       Access: Published
//  Description: Sets a boolean specifying whether the terrain will
//               be rendered bruteforce. If the terrain is rendered
//               bruteforce, there will be no Level of Detail, and
//               the update() call will only update the
//               terrain if it is marked dirty.
////////////////////////////////////////////////////////////////////
INLINE void GeoMipTerrain::
set_bruteforce(bool bf) {
  if (bf == true && _bruteforce == false) {
    _is_dirty = true;
  }
  _bruteforce = bf;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_bruteforce
//       Access: Published
//  Description: Returns a boolean whether the terrain is rendered
//               bruteforce or not. See set_bruteforce for more
//               information.
////////////////////////////////////////////////////////////////////
INLINE bool GeoMipTerrain::
get_bruteforce() {
  return _bruteforce;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::set_auto_flatten
//       Access: Private
//  Description: The terrain can be automatically flattened (using
//               flatten_light, flatten_medium, or flatten_strong)
//               after each update.  This only affects future
//               updates, it doesn't flatten the current terrain.
////////////////////////////////////////////////////////////////////
INLINE void GeoMipTerrain::
set_auto_flatten(int mode) {
  _auto_flatten = mode;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::set_focal_point
//       Access: Published
//  Description: Sets the focal point.  GeoMipTerrain generates
//               high-resolution terrain around the focal point, and
//               progressively lower and lower resolution terrain
//               as you get farther away. If a point is supplied
//               and not a NodePath, make sure it's relative to
//               the terrain. Only the x and y coordinates of
//               the focal point are taken in respect.
////////////////////////////////////////////////////////////////////
INLINE void GeoMipTerrain::
set_focal_point(double x, double y) {
  if (!_focal_is_temporary) {
    PT(PandaNode) tmpnode = new PandaNode("tmp_focal");
    _focal_point = NodePath(tmpnode);
  }
  _focal_point.set_pos(_root, x, y, 0);
  _focal_is_temporary = true;
}
INLINE void GeoMipTerrain::
set_focal_point(const LPoint2d &fp) {
  set_focal_point(fp.get_x(), fp.get_y());
}
INLINE void GeoMipTerrain::
set_focal_point(const LPoint2f &fp) {
  set_focal_point(double(fp.get_x()), double(fp.get_y()));
}
INLINE void GeoMipTerrain::
set_focal_point(const LPoint3d &fp) {
  set_focal_point(fp.get_x(), fp.get_y());
}
INLINE void GeoMipTerrain::
set_focal_point(const LPoint3f &fp) {
  set_focal_point(double(fp.get_x()), double(fp.get_y()));
}
INLINE void GeoMipTerrain::
set_focal_point(NodePath fp) {
  if (_focal_is_temporary) {
    _focal_point.remove_node();
  }
  _focal_point = fp;
  _focal_is_temporary = false;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_focal_point
//       Access: Published
//  Description: Returns the focal point, as a NodePath.
//               If you have set it to be just a point, it will
//               return an empty node at the focal position.
////////////////////////////////////////////////////////////////////
INLINE NodePath GeoMipTerrain::
get_focal_point() const {
  return _focal_point;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_root
//       Access: Published
//  Description: Returns the root of the terrain.  This is a
//               single PandaNode to which all the rest of the
//               terrain is parented.  The generate and update
//               operations replace the nodes which are parented
//               to this root, but they don't replace this root
//               itself.
////////////////////////////////////////////////////////////////////
INLINE NodePath GeoMipTerrain::
get_root() const {
  return _root;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::set_min_level
//       Access: Published
//  Description: Sets the minimum level of detail at which blocks
//               may be generated by generate() or update().
//               The default value is 0, which is the highest
//               quality. This value is also taken in respect when
//               generating the terrain bruteforce.
////////////////////////////////////////////////////////////////////
INLINE void GeoMipTerrain::
set_min_level(unsigned short minlevel) {
  _min_level = minlevel;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_min_level
//       Access: Published
//  Description: Gets the minimum level of detail at which blocks
//               may be generated by generate() or update().
//               The default value is 0, which is the highest
//               quality.
////////////////////////////////////////////////////////////////////
INLINE unsigned short GeoMipTerrain::
get_min_level() {
  return _min_level;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_max_level
//       Access: Published
//  Description: Returns the highest level possible for this block
//               size. When a block is at this level, it will be
//               the worst quality possible.
////////////////////////////////////////////////////////////////////
INLINE unsigned short GeoMipTerrain::
get_max_level() {
  return _max_level;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_block_size
//       Access: Published
//  Description: Gets the block size.
////////////////////////////////////////////////////////////////////
INLINE unsigned short GeoMipTerrain::
get_block_size() {
  return _block_size;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::set_block_size
//       Access: Published
//  Description: Sets the block size. If it is not a power of two,
//               the closest power of two is used.
////////////////////////////////////////////////////////////////////
INLINE void GeoMipTerrain::
set_block_size(unsigned short newbs) {
  if (is_power_of_two(newbs)) {
    _block_size = newbs;
  } else {
    if (is_power_of_two(newbs - 1)) {
      _block_size = newbs - 1;
    } else {
      if (is_power_of_two(newbs + 1)) {
        _block_size = newbs + 1;
      } else {
        _block_size = (unsigned short) pow(2.0,
                            floor(log((double) newbs) / log(2.0) + 0.5));
      }
    }
  }
  _max_level = (unsigned short) (log((double) _block_size) / log(2.0));
  _is_dirty = true;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::is_dirty
//       Access: Published
//  Description: Returns a bool indicating whether the terrain is
//               marked 'dirty', that means the terrain has to be
//               regenerated on the next update() call, because
//               for instance the heightfield has changed.
//               Once the terrain has been regenerated, the dirty
//               flag automatically gets reset internally.
////////////////////////////////////////////////////////////////////
INLINE bool GeoMipTerrain::
is_dirty() {
  return _is_dirty;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::set_factor
//       Access: Published
//  Description: DEPRECATED method. Use set_near/far instead.
//               Sets the quality factor at which blocks must be
//               generated. The higher this level, the better
//               quality the terrain will be, but more expensive
//               to render. A value of 0 makes the terrain the
//               lowest quality possible, depending on blocksize.
//               The default value is 100.
////////////////////////////////////////////////////////////////////
INLINE void GeoMipTerrain::
set_factor(PN_stdfloat factor) {
  grutil_cat.debug() << "Using deprecated method set_factor, use set_near and set_far instead!\n";
  _use_near_far = false;
  _factor = factor;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::set_near_far
//       Access: Published
//  Description: Sets the near and far LOD distances in one call.
////////////////////////////////////////////////////////////////////
INLINE void GeoMipTerrain::
set_near_far(double input_near, double input_far) {
  _use_near_far = true;
  _near = input_near;
  _far = input_far;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::set_near
//       Access: Published
//  Description: Sets the near LOD distance, at which the terrain
//               will be rendered at highest quality.
//               This distance is in the terrain's coordinate space!
////////////////////////////////////////////////////////////////////
INLINE void GeoMipTerrain::
set_near(double input_near) {
  _use_near_far = true;
  _near = input_near;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::set_far
//       Access: Published
//  Description: Sets the far LOD distance, at which the terrain
//               will be rendered at lowest quality.
//               This distance is in the terrain's coordinate space!
////////////////////////////////////////////////////////////////////
INLINE void GeoMipTerrain::
set_far(double input_far) {
  _use_near_far = true;
  _far = input_far;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_far
//       Access: Published
//  Description: Returns the far LOD distance in the terrain coordinate
//               space
////////////////////////////////////////////////////////////////////
INLINE double GeoMipTerrain::
get_far() {
  return _far;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_near
//       Access: Published
//  Description: Returns the near LOD distance in the terrain coordinate
//               space
////////////////////////////////////////////////////////////////////
INLINE double GeoMipTerrain::
get_near() {
  return _near;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_flatten_mode
//       Access: Published
//  Description: Returns the automatic-flatten mode (e.g., off,
//               flatten_light, flatten_medium, or flatten_strong)
////////////////////////////////////////////////////////////////////
INLINE int GeoMipTerrain::
get_flatten_mode() {
  return _auto_flatten;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_block_node_path
//       Access: Published
//  Description: Returns the NodePath of the specified block.
//               If auto-flatten is enabled and the node is
//               getting removed during the flattening process,
//               it will still return a NodePath with the
//               appropriate terrain chunk, but it will be in
//               a temporary scenegraph.
//               Please note that this returns a const object and
//               you can not modify the node. Modify the heightfield
//               instead.
////////////////////////////////////////////////////////////////////
INLINE const NodePath GeoMipTerrain::
get_block_node_path(unsigned short mx, unsigned short my) {
  nassertr(mx < _blocks.size(), NodePath::fail());
  nassertr(my < _blocks[mx].size(), NodePath::fail());
  return _blocks[mx][my];
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_block_from_pos
//       Access: Published
//  Description: Gets the coordinates of the block at the specified
//               position. This position must be relative to the
//               terrain, not to render. Returns an array containing
//               two values: the block x and the block y coords.
//               If the positions are out of range, the closest
//               block is taken.
//               Note that the VecBase returned does not represent
//               a vector, position, or rotation, but it contains
//               the block index of the block which you can use
//               in GeoMipTerrain::get_block_node_path.
////////////////////////////////////////////////////////////////////
INLINE LVecBase2 GeoMipTerrain::
get_block_from_pos(double x, double y) {
  if (x < 0) x = 0;
  if (y < 0) y = 0;
  if (x > _xsize - 1) x = _xsize - 1;
  if (y > _ysize - 1) y = _ysize - 1;
  x = floor(x / _block_size);
  y = floor(y / _block_size);
  return LVecBase2(x, y);
}
////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::lod_decide
//       Access: Private
//  Description: Calculates the level for the given mipmap.
////////////////////////////////////////////////////////////////////
INLINE unsigned short GeoMipTerrain::
lod_decide(unsigned short mx, unsigned short my) {
  PN_stdfloat cx = mx;
  PN_stdfloat cy = my;
  cx = (cx * _block_size + _block_size / 2) * _root.get_sx();
  cy = (cy * _block_size + _block_size / 2) * _root.get_sy();
  PN_stdfloat d;
  if (_use_near_far) {
    d = sqrt(pow(_focal_point.get_x(_root) - cx, 2) +
             pow(_focal_point.get_y(_root) - cy, 2));
    if (d < _near) {
      return 0;
    } else if (d > _far) {
      return _max_level;
    } else {
      return (unsigned short)((d - _near) / (_far - _near) * _max_level * (1.0 - (_min_level / _max_level)) + _min_level);
    }
  } else {
    if (_factor > 0.0) {
      d = sqrt(pow(_focal_point.get_x(_root) - cx, 2) +
               pow(_focal_point.get_y(_root) - cy, 2)) / _factor;
    } else {
      d = _max_level;
    }
    return short(floor(d));
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::set_heightfield
//       Access: Published
//  Description: Loads the specified heightmap image file into
//               the heightfield. Returns true if succeeded, or
//               false if an error has occured.
//               If the heightmap is not a power of two plus one,
//               it is scaled up using a gaussian filter.
////////////////////////////////////////////////////////////////////
INLINE bool GeoMipTerrain::
set_heightfield(const PNMImage &image) {
  if (image.get_color_space() == CS_sRGB) {
    // Probably a mistaken metadata setting on the file.
    grutil_cat.warning()
      << "Heightfield image is specified to have sRGB color space!\n"
         "Panda applies gamma correction, which will probably cause "
         "it to produce incorrect results.\n";
  }

  // Before we apply anything, validate the size.
  if (is_power_of_two(image.get_x_size() - 1) &&
      is_power_of_two(image.get_y_size() - 1)) {
    _heightfield = image;
    _is_dirty = true;
    _xsize = _heightfield.get_x_size();
    _ysize = _heightfield.get_y_size();
    return true;
  } else {
    grutil_cat.error()
      << "Specified image does not have a power-of-two-plus-one size!\n";
  }
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::set_color_map
//       Access: Published
//  Description: Loads the specified image as color map. The next
//               time generate() is called, the terrain is painted
//               with this color map using the vertex color column.
//               Returns a boolean indicating whether the operation
//               has succeeded.
////////////////////////////////////////////////////////////////////
INLINE bool GeoMipTerrain::
set_color_map(const Filename &filename, PNMFileType *ftype) {
  if (_color_map.read(filename, ftype)) {
    _is_dirty = true;
    _has_color_map = true;
    return true;
  }
  return false;
}

INLINE bool GeoMipTerrain::
set_color_map(const PNMImage &image) {
  _color_map.copy_from(image);
  _is_dirty = true;
  _has_color_map = true;
  return true;
}

INLINE bool GeoMipTerrain::
set_color_map(const Texture *tex) {
  tex->store(_color_map);
  _is_dirty = true;
  return true;
}

INLINE bool GeoMipTerrain::
set_color_map(const string &path) {
  return set_color_map(Filename(path));
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::has_color_map
//       Access: Published
//  Description: Returns whether a color map has been set.
////////////////////////////////////////////////////////////////////
INLINE bool GeoMipTerrain::
has_color_map() const {
  return _has_color_map;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::clear_color_map
//       Access: Published
//  Description: Clears the color map.
////////////////////////////////////////////////////////////////////
INLINE void GeoMipTerrain::
clear_color_map() {
  if (_has_color_map) {
    _color_map.clear();
    _has_color_map = false;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::set_border_stitching
//       Access: Published
//  Description: If this value is true, the LOD level at the
//               borders of the terrain will be 0. This is useful
//               if you have multiple terrains attached and you
//               want to stitch them together, to fix seams.
//               This setting also has effect when bruteforce is
//               enabled, although in that case you are probably
//               better off with setting the minlevels to the same
//               value.
////////////////////////////////////////////////////////////////////
INLINE void GeoMipTerrain::
set_border_stitching(bool stitching) {
  if (stitching && !_stitching) {
    _is_dirty = true;
  }
  _stitching = stitching;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_stitching
//       Access: Published
//  Description: Returns the current stitching setting. False by
//               default, unless set_stitching has been set.
////////////////////////////////////////////////////////////////////
INLINE bool GeoMipTerrain::
get_border_stitching() {
  return _stitching;
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_pixel_value
//       Access: Private
//  Description: Get the elevation at a certain pixel of the image.
//               This function does NOT linearly interpolate.
//               For that, use GeoMipTerrain::get_elevation() instead.
////////////////////////////////////////////////////////////////////
INLINE double GeoMipTerrain::
get_pixel_value(int x, int y) {
  x = max(min(x,int(_xsize-1)),0);
  y = max(min(y,int(_ysize-1)),0);
  if (_heightfield.is_grayscale()) {
    return double(_heightfield.get_bright(x, y));
  } else {
    return double(_heightfield.get_red(x, y))
         + double(_heightfield.get_green(x, y)) / 256.0
         + double(_heightfield.get_blue(x, y)) / 65536.0;
  }
}
INLINE double GeoMipTerrain::
get_pixel_value(unsigned short mx, unsigned short my, int x, int y) {
  nassertr_always(mx < (_xsize - 1) / _block_size, false);
  nassertr_always(my < (_ysize - 1) / _block_size, false);
  return get_pixel_value(mx * _block_size + x, (_ysize - 1) -
                         (my * _block_size + y));
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::get_normal
//       Access: Published
//  Description: Fetches the terrain normal at (x,y), where the input
//               coordinate is specified in pixels. This ignores the
//               current LOD level and instead provides an
//               accurate number.
//               Terrain scale is NOT taken into account! To get
//               accurate normals, please divide it by the
//               terrain scale and normalize it again!
////////////////////////////////////////////////////////////////////
INLINE LVector3 GeoMipTerrain::
get_normal(unsigned short mx, unsigned short my, int x, int y) {
  nassertr_always(mx < (_xsize - 1) / _block_size, false);
  nassertr_always(my < (_ysize - 1) / _block_size, false);
  return get_normal(mx * _block_size + x, (_ysize - 1) -
                    (my * _block_size + y));
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::is_power_of_two
//       Access: Private
//  Description: Returns a bool whether the given int i is a
//               power of two or not.
////////////////////////////////////////////////////////////////////
INLINE bool GeoMipTerrain::
is_power_of_two(unsigned int i) {
  return !((i - 1) & i);
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::f_part
//       Access: Private
//  Description: Returns the part of the number right of the
//               floating-point.
////////////////////////////////////////////////////////////////////
INLINE float GeoMipTerrain::
f_part(float i) {
  return i - floor(i);
}
INLINE double GeoMipTerrain::
f_part(double i) {
  return i - floor(i);
}

////////////////////////////////////////////////////////////////////
//     Function: GeoMipTerrain::sfav
//       Access: Private
//  Description: Used to calculate vertex numbers. Only to
//               be used internally.
////////////////////////////////////////////////////////////////////
INLINE int GeoMipTerrain::
sfav(int n, int powlevel, int mypowlevel) {
  double t = n - 1;
  t /= pow(2.0, powlevel - mypowlevel);
  t = double(int(t > 0.0 ? t + 0.5 : t - 0.5));
  t *= pow(2.0, powlevel - mypowlevel);
  return int(t);
}

